import Foundation

enum GameState: Equatable {
    case playing
    case won
    case lost(reason: LossReason)
    
    enum LossReason {
        case steppedOnMine
        case timeExpired
    }
}

struct Cell {
    var isMine: Bool
    var neighborMines: Int = 0
    var state: CellState = .hidden
}

enum CellState {
    case hidden
    case revealed
    case flagged
    case questioned
}

/// 游戏棋盘坐标系统
/// - 参数说明:
///   - x: 横向坐标 (0...width-1)
///   - y: 纵向坐标 (0...height-1)
struct Coordinate: Hashable {
    let x: Int
    let y: Int
}

protocol GameBoardDelegate: AnyObject {
    func gameStateDidChange(_ state: GameState)
}

class GameBoard {
    let difficulty: DifficultyLevel
    let width: Int
    let height: Int
    var cells: [[Cell]]
    private var unrevealedSafeCells = 0
    var gameState: GameState = .playing {
        didSet {
            delegate?.gameStateDidChange(gameState)
        }
    }
    weak var delegate: GameBoardDelegate?
    
    init(difficulty: DifficultyLevel) {
        self.difficulty = difficulty
        let config = difficulty.config
        self.width = config.width
        self.height = config.height
        self.cells = Array(repeating: Array(repeating: Cell(isMine: false), count: config.height), 
                          count: config.width)
        generateMinesOptimized(totalMines: config.mines)
        calculateNeighborMines()
        unrevealedSafeCells = totalSafeCells()
    }
    
    private func totalSafeCells() -> Int {
        cells.flatMap { $0 }.filter { !$0.isMine }.count
    }
    
    private func generateMinesOptimized(totalMines: Int) {
        var allCoordinates = (0..<width).flatMap { x in
            (0..<height).map { y in Coordinate(x: x, y: y) }
        }
        
        // Fisher-Yates shuffle
        for i in 0..<min(totalMines, allCoordinates.count) {
            let j = Int.random(in: i..<allCoordinates.count)
            allCoordinates.swapAt(i, j)
        }
        
        for coordinate in allCoordinates.prefix(totalMines) {
            var cell = cells[coordinate.x][coordinate.y]
            cell.isMine = true
            cells[coordinate.x][coordinate.y] = cell
        }
    }
    
    private func calculateNeighborMines() {
        for x in 0..<width {
            for y in 0..<height {
                var count = 0
                // 检查周围8个格子
                for dx in -1...1 {
                    for dy in -1...1 {
                        guard dx != 0 || dy != 0 else { continue } // 排除自己
                        let nx = x + dx
                        let ny = y + dy
                        guard nx >= 0, nx < width, ny >= 0, ny < height else { continue }
                        if cells[nx][ny].isMine {
                            count += 1
                        }
                    }
                }
                cells[x][y].neighborMines = count
            }
        }
    }
    
    /// 验证坐标是否在棋盘有效范围内
    private func isValidCoordinate(_ coordinate: Coordinate) -> Bool {
        coordinate.x >= 0 && coordinate.x < width &&
        coordinate.y >= 0 && coordinate.y < height
    }
    
    /// 安全访问单元格的subscript方法
    /// - 参数 coordinate: 要访问的棋盘坐标
    /// - 返回值: 可选类型的Cell，当坐标无效时返回nil
    subscript(coordinate: Coordinate) -> Cell? {
        guard isValidCoordinate(coordinate) else { return nil }
        return cells[coordinate.x][coordinate.y]
    }
    
    /// 计算指定坐标周围地雷数量
    /// - Parameters:
    ///   - x: X轴坐标
    ///   - y: Y轴坐标
    /// - Returns: 周围8格中的地雷数量（0-8）
    private func countNeighborMines(x: Int, y: Int) -> Int {
        let current = Coordinate(x: x, y: y)
        return (-1...1).flatMap { dx in
            (-1...1).map { dy in
                Coordinate(x: current.x + dx, y: current.y + dy)
            }
        }.filter { coord in
            coord != current && (self[coord]?.isMine ?? false)
        }.count
    }
    
    /// 揭示指定坐标的单元格
    /// - 参数 position: 要揭示的棋盘坐标
    func reveal(at position: Coordinate) {
        guard var cell = self[position], case .hidden = cell.state else { return }
        
        cell.state = .revealed
        cells[position.x][position.y] = cell
        
        if cell.isMine {
            gameState = .lost(reason: .steppedOnMine)
            revealAllMines()
            return
        }
        
        unrevealedSafeCells -= 1
        
        if cell.neighborMines == 0 {
            floodReveal(from: position)
        }
        
        checkWinCondition()
    }
    
    private func revealAllMines() {
        for x in 0..<width {
            for y in 0..<height {
                if cells[x][y].isMine {
                    cells[x][y].state = .revealed
                }
            }
        }
    }
    
    func floodReveal(from position: Coordinate) {
        var queue = [position]
        var visited = Set<Coordinate>()
        
        while let current = queue.first {
            queue.removeFirst()
            
            guard !visited.contains(current) else { continue }
            visited.insert(current)
            
            for dx in -1...1 {
                for dy in -1...1 {
                    let neighbor = Coordinate(x: current.x + dx, y: current.y + dy)
                    guard var neighborCell = self[neighbor],
                          case .hidden = neighborCell.state,
                          !neighborCell.isMine else {
                        continue
                    }
                    
                    neighborCell.state = .revealed
                    cells[neighbor.x][neighbor.y] = neighborCell
                    if neighborCell.neighborMines == 0 {
                        queue.append(neighbor)
                    }
                }
            }
        }
    }
    
    /// 切换单元格标记状态（旗标/问号）
    /// - 参数 position: 要操作的棋盘坐标
    func toggleFlag(at position: Coordinate) {
        guard gameState == .playing, var cell = self[position] else { return }
        
        switch cell.state {
        case .hidden:
            cell.state = .flagged
        case .flagged:
            cell.state = .questioned
        case .questioned:
            cell.state = .hidden
        default: break
        }
        cells[position.x][position.y] = cell
    }
    
    private func checkWinCondition() {
        gameState = unrevealedSafeCells == 0 ? .won : .playing
    }
}
